# ending : utf-8
"""
@Time : 2025/6/9
@Author : xu qian
@File : 全国电动汽车用户数据分析平台.py
"""
# -*- coding: utf-8 -*-

from pyecharts.charts import Bar, Line, Sunburst, Pie, Timeline, Page
from pyecharts import options as opts
import pandas as pd
import warnings
import os

# 忽略FutureWarning警告
warnings.filterwarnings('ignore', category=FutureWarning)

# 全局样式设置
BG_COLOR = "#000000"  # 黑色背景
TEXT_COLOR = "#FFFFFF"  # 白色文字
CHART_THEME = "dark"  # 使用暗色主题

# 1.读取数据
data = pd.read_excel('用户信息表格.xlsx')

# 检查数据列是否存在，如果存在则分割智能化水平字段
if '电动汽车智能化水平（自动驾驶等级/OTA频率）' in data.columns:
    # 分割字符串并提取两部分
    split_col = data['电动汽车智能化水平（自动驾驶等级/OTA频率）'].str.split('/', expand=True)
    # 提取自动驾驶等级（第一部分）
    data['自动驾驶等级'] = split_col[0].str.strip()
    # 提取OTA频率（第二部分）
    data['OTA频率'] = split_col[1].str.strip()

# 确保result目录存在
os.makedirs('result1', exist_ok=True)


def create_age_distribution_bar(data: pd.DataFrame):
    """
    创建用户年龄分布柱状图
    参数:
        data: 包含用户年龄数据的DataFrame
    返回:
        Bar: 配置好的柱状图对象
    """
    # 确保数据是数值型
    data['用户年龄'] = pd.to_numeric(data['用户年龄'], errors='coerce')
    data = data.dropna(subset=['用户年龄'])

    # 将年龄分段处理
    bins = [20, 30, 40, 50, 60]
    labels = ['20-29岁', '30-39岁', '40-49岁', '50-59岁']
    data['年龄段'] = pd.cut(data['用户年龄'], bins=bins, labels=labels, right=False)

    # 统计各年龄段用户数量
    age_counts = data['年龄段'].value_counts().sort_index()

    # 创建柱状图
    bar = (
        Bar(init_opts=opts.InitOpts(width='900px', height='500px', bg_color=BG_COLOR, theme=CHART_THEME))
            .add_xaxis(age_counts.index.tolist())
            .add_yaxis(
            series_name="用户数量",
            y_axis=age_counts.values.tolist(),
            itemstyle_opts=opts.ItemStyleOpts(color="#5793F3", border_radius=4, opacity=0.8),
            label_opts=opts.LabelOpts(position="top", font_size=12, color=TEXT_COLOR)
        )
            .set_global_opts(
            title_opts=opts.TitleOpts(
                title="电动汽车用户年龄分布",
                pos_left="center",
                title_textstyle_opts=opts.TextStyleOpts(
                    font_size=18,
                    font_weight="bold",
                    color=TEXT_COLOR
                )
            ),
            xaxis_opts=opts.AxisOpts(
                name="年龄段",
                axislabel_opts=opts.LabelOpts(color=TEXT_COLOR)
            ),
            yaxis_opts=opts.AxisOpts(
                name="用户数量",
                name_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR),
                axislabel_opts=opts.LabelOpts(color=TEXT_COLOR)
            ),
            tooltip_opts=opts.TooltipOpts(
                trigger="axis",
                formatter="{b}: {c}人",
                background_color=BG_COLOR,
                border_color="#333",
                textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
            ),
            visualmap_opts=opts.VisualMapOpts(
                min_=min(age_counts),
                max_=max(age_counts),
                orient="horizontal",
                pos_left="center",
                range_color=["#D7E7FF", "#5793F3"],
                textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
            )
        )
    )
    bar.render('result1/用户年龄分布.html')
    return bar


def create_age_brand_bar(data: pd.DataFrame):
    # 确保数据是数值型
    data['用户年龄'] = pd.to_numeric(data['用户年龄'], errors='coerce')
    data = data.dropna(subset=['用户年龄'])

    # 将年龄分段处理
    bins = [20, 30, 40, 50, 60]
    labels = ['20-29岁', '30-39岁', '40-49岁', '50-59岁']
    data['年龄段'] = pd.cut(data['用户年龄'], bins=bins, labels=labels, right=False)

    # 统计各年龄段购买各品牌的数量
    grouped = data.groupby(['年龄段', '电动汽车品牌'], observed=True).size().unstack().fillna(0)
    brands = grouped.sum().sort_values(ascending=False).index.tolist()

    bar = Bar(init_opts=opts.InitOpts(width='1000px', height='600px', bg_color=BG_COLOR, theme=CHART_THEME))
    for age_group in grouped.index:
        bar.add_xaxis(brands)
        bar.add_yaxis(
            series_name=age_group,
            y_axis=grouped.loc[age_group, brands].values.tolist(),
            label_opts=opts.LabelOpts(is_show=False),
            itemstyle_opts=opts.ItemStyleOpts(opacity=0.8, border_radius=4)
        )

    bar.set_global_opts(
        title_opts=opts.TitleOpts(
            title="各年龄段用户电动汽车品牌偏好",
            pos_left="center",
            title_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
        ),
        xaxis_opts=opts.AxisOpts(
            axislabel_opts=opts.LabelOpts(rotate=45, color=TEXT_COLOR),
            name="汽车品牌",
            name_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
        ),
        yaxis_opts=opts.AxisOpts(
            name="用户数量",
            name_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR),
            axislabel_opts=opts.LabelOpts(color=TEXT_COLOR)
        ),
        tooltip_opts=opts.TooltipOpts(
            trigger="axis",
            background_color=BG_COLOR,
            border_color="#333",
            textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
        ),
        legend_opts=opts.LegendOpts(
            pos_top="10%",
            textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
        ),
        datazoom_opts=[opts.DataZoomOpts()]
    )
    bar.render('result1/年龄段品牌偏好柱状图.html')
    return bar

def create_pie_chart(data: pd.DataFrame, groupby_col: str, output_name: str):
    """
    创建品牌占比饼图（按指定列分组）
    参数:
        data: 包含分组和品牌数据的DataFrame
        groupby_col: 用于分组的列名
        output_name: 输出文件名
    返回:
        Timeline: 配置好的时间线饼图对象
    """
    if groupby_col not in data.columns:
        print(f"警告: 数据中不存在列 '{groupby_col}'，跳过生成 {output_name}.html")
        return None

    # 统计各分组购买各品牌的数量
    grouped = data.groupby([groupby_col, '电动汽车品牌'], observed=True).size().unstack().fillna(0)
    data_pair_dict = {}

    for group in grouped.index:
        total = grouped.loc[group].sum()
        data_pair_dict[group] = [[brand, round(grouped.loc[group, brand] / total, 4)]
                                 for brand in grouped.columns]

    timeline = Timeline(
        init_opts=opts.InitOpts(
            width='900px',
            height='600px',
            bg_color=BG_COLOR,
            theme=CHART_THEME
        )
    )

    for group in data_pair_dict:
        pie = (
            Pie()
                .add(
                series_name="品牌占比",
                data_pair=data_pair_dict[group],
                radius=["30%", "75%"],
                label_opts=opts.LabelOpts(
                    is_show=True,
                    formatter="{b}: {d}%",
                    position="outside",
                    color=TEXT_COLOR
                ),
                itemstyle_opts=opts.ItemStyleOpts(
                    border_width=1,
                    border_color="#333"
                )
            )
                .set_global_opts(
                title_opts=opts.TitleOpts(
                    title=f"{group}用户电动汽车品牌占比",
                    pos_left="center",
                    title_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
                ),
                legend_opts=opts.LegendOpts(
                    orient="vertical",
                    pos_right="5%",
                    textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
                ),
                tooltip_opts=opts.TooltipOpts(
                    trigger="item",
                    formatter="{a} <br/>{b}: {c} ({d}%)",
                    background_color=BG_COLOR,
                    border_color="#333",
                    textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
                )
            )
        )
        timeline.add(pie, group)

    timeline.render(f'result1/{output_name}.html')
    return timeline


def create_gender_brand_line(data: pd.DataFrame):
    """
    创建性别品牌偏好折线图
    参数:
        data: 包含性别和品牌数据的DataFrame
    返回:
        Line: 配置好的折线图对象
    """
    if '用户性别' not in data.columns or '电动汽车品牌' not in data.columns:
        print("警告: 缺少必要的列 '用户性别' 或 '电动汽车品牌'，跳过生成性别品牌偏好折线图")
        return None

    # 统计各性别购买各品牌的数量
    cross_tab = pd.crosstab(data['电动汽车品牌'], data['用户性别']).sort_values(by=['男', '女'], ascending=False)

    line = (
        Line(init_opts=opts.InitOpts(
            width="1000px",
            height="600px",
            bg_color=BG_COLOR,
            theme=CHART_THEME
        ))
            .add_xaxis(cross_tab.index.tolist())
            .add_yaxis(
            "男性用户",
            cross_tab['男'].tolist(),
            symbol="circle",
            symbol_size=8,
            label_opts=opts.LabelOpts(color=TEXT_COLOR)
        )
            .add_yaxis(
            "女性用户",
            cross_tab['女'].tolist(),
            symbol="diamond",
            symbol_size=8,
            label_opts=opts.LabelOpts(color=TEXT_COLOR)
        )
            .set_global_opts(
            title_opts=opts.TitleOpts(
                title="不同性别用户电动汽车品牌偏好",
                pos_left="center",
                title_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
            ),
            xaxis_opts=opts.AxisOpts(
                axislabel_opts=opts.LabelOpts(rotate=45, color=TEXT_COLOR),
                name="汽车品牌",
                name_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
            ),
            yaxis_opts=opts.AxisOpts(
                name="用户数量",
                name_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR),
                axislabel_opts=opts.LabelOpts(color=TEXT_COLOR)
            ),
            tooltip_opts=opts.TooltipOpts(
                trigger="axis",
                background_color=BG_COLOR,
                border_color="#333",
                textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
            ),
            legend_opts=opts.LegendOpts(
                pos_top="10%",
                textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
            ),
            datazoom_opts=[opts.DataZoomOpts()]
        )
    )
    line.render('result1/性别品牌偏好折线图.html')
    return line


def create_brand_model_sunburst(data: pd.DataFrame):
    """
    创建品牌车型旭日图
    参数:
        data: 包含品牌和车型数据的DataFrame
    返回:
        Sunburst: 配置好的旭日图对象
    """
    if '电动汽车品牌' not in data.columns or '车型' not in data.columns:
        print("警告: 缺少必要的列 '电动汽车品牌' 或 '车型'，跳过生成品牌车型旭日图")
        return None

    # 统计品牌-车型组合的数量
    grouped = data.groupby(['电动汽车品牌', '车型'], observed=True).size().reset_index(name='count')
    total = grouped['count'].sum()

    sunburst_data = []
    for brand in grouped['电动汽车品牌'].unique():
        brand_data = {"name": brand, "children": []}
        brand_total = grouped[grouped['电动汽车品牌'] == brand]['count'].sum()

        for _, row in grouped[grouped['电动汽车品牌'] == brand].iterrows():
            percentage = (row['count'] / total) * 100
            display_value = f"{row['count']}辆 ({percentage:.1f}%)" if percentage >= 0.5 else f"{row['count']}辆 (<0.5%)"
            brand_data["children"].append({
                "name": row['车型'],
                "value": row['count'],
                "label": {"formatter": display_value}
            })
        sunburst_data.append(brand_data)

    sunburst = (
        Sunburst(init_opts=opts.InitOpts(
            width="1000px",
            height="800px",
            bg_color=BG_COLOR,
            theme=CHART_THEME
        ))
            .add(
            series_name="",
            data_pair=sunburst_data,
            highlight_policy="ancestor",
            radius=[0, "90%"],
            levels=[
                {"r0": "5%", "r": "20%", "label": {"rotate": "radial"}},
                {"r0": "20%", "r": "80%", "label": {"rotate": "tangential"}}
            ]
        )
            .set_global_opts(
            title_opts=opts.TitleOpts(
                title="电动汽车品牌与车型分布",
                pos_left="center",
                title_textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
            ),
            tooltip_opts=opts.TooltipOpts(
                trigger="item",
                formatter="{b}: {c}辆 ({d}%)",
                background_color=BG_COLOR,
                border_color="#333",
                textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)
            )
        )
            .set_series_opts(
            label_opts=opts.LabelOpts(
                position="outside",
                padding=5,
                formatter="{b}: {c}",
                color=TEXT_COLOR
            )
        )
    )
    sunburst.render('result1/品牌车型旭日图.html')
    return sunburst


def create_title(title_str: str):
    """
    创建标题图表
    参数:
        title_str: 标题文字
    返回:
        Pie: 配置好的标题图表对象
    """
    title = Pie(init_opts=opts.InitOpts(
        width="800px",
        height="200px",
        bg_color=BG_COLOR,
        theme=CHART_THEME
    ))
    title.set_global_opts(
        title_opts=opts.TitleOpts(
            title=title_str,
            title_textstyle_opts=opts.TextStyleOpts(
                font_size=36,
                color=TEXT_COLOR
            ),
            pos_left='center',
            pos_top='middle'
        )
    )
    return title


def create_key_index(title: str, sub_title: str):
    """
    创建关键指标图表
    参数:
        title: 标题文字
        sub_title: 副标题/数值
    返回:
        Pie: 配置好的关键指标图表对象
    """
    key_chart = Pie(init_opts=opts.InitOpts(
        width='300px',
        height='300px',
        bg_color=BG_COLOR,
        theme=CHART_THEME
    ))
    key_chart.set_global_opts(
        title_opts=opts.TitleOpts(
            title=title,
            subtitle=str(sub_title),
            title_textstyle_opts=opts.TextStyleOpts(
                font_size=24,
                color=TEXT_COLOR
            ),
            pos_left='center',
            pos_top='middle'
        )
    )
    return key_chart


# 生成所有图表
print("开始生成可视化图表...")

# 生成各独立图表
print("正在生成用户年龄分布图...")
age_dist_chart = create_age_distribution_bar(data)

print("正在生成年龄段品牌偏好图...")
age_brand_chart = create_age_brand_bar(data)

print("正在生成性别品牌偏好图...")
gender_brand_chart = create_gender_brand_line(data)

print("正在生成品牌车型旭日图...")
sunburst_chart = create_brand_model_sunburst(data)

# 生成各种饼图
pie_charts = []
if '电动汽车过户状态' in data.columns:
    print("正在生成电动汽车过户状态与品牌占比图...")
    pie_charts.append(create_pie_chart(data, '电动汽车过户状态', '电动汽车过户状态与电动汽车品牌占比'))
if '用户职业' in data.columns:
    print("正在生成用户职业与品牌占比图...")
    pie_charts.append(create_pie_chart(data, '用户职业', '用户职业与电动汽车品牌占比'))
if '使用场景' in data.columns:
    print("正在生成用户使用场景与品牌占比图...")
    pie_charts.append(create_pie_chart(data, '使用场景', '用户使用场景与电动汽车品牌占比'))
if 'OTA频率' in data.columns:
    print("正在生成用户OTA频率与品牌占比图...")
    pie_charts.append(create_pie_chart(data, 'OTA频率', '用户OTA频率与电动汽车品牌占比'))

# 创建可视化大屏
print("正在生成可视化大屏...")
page = Page(
    page_title='用户使用情况可视化大屏',
    layout=Page.DraggablePageLayout
)

# 添加背景颜色（通过添加一个全屏的Pie图表实现）
background = Pie(init_opts=opts.InitOpts(width="100%", height="100%", bg_color=BG_COLOR))
background.set_global_opts()
page.add(background)

# 添加标题
page.add(create_title('用户使用情况可视化大屏'))

# 添加主要图表
page.add(
    age_dist_chart,
    age_brand_chart,
    gender_brand_chart,
    sunburst_chart
)

# 添加饼图
for chart in pie_charts:
    if chart is not None:
        page.add(chart)

# 渲染大屏
# page.render('result1/render1.html')
# 重新保存html文件
page.save_resize_html('render1.html',
                     cfg_file=r'E:\Python\PyCharm Community Edition 2021.2.2\PythonProject1\PythonProject\templates\chart_config (4).json',
                      dest=r"resize_render1.html",)
print("所有图表已成功生成到result1目录下")
# print("可视化大屏已生成: result1/render1.html")